package h2d;

/**
	h2d.Drawable is the base class for all 2D objects that will draw something on screen.
	Unlike Object base class, all properties of Drawable only apply to the current object and are not inherited by its children.
**/
class Drawable extends Object {

	/**
		The color multiplier for the object. Can be used to adjust individually each of the four channels R,G,B,A (default [1,1,1,1])
	**/
	public var color(default,default) : h3d.Vector;

	/**
		By enabling smoothing, scaling the object up or down will use hardware bilinear filtering resulting in less crisp aspect.
		By default smooth is null and then Scene.defaultSmooth value is used.
	**/
	public var smooth : Null<Bool>;

	/**
		By enabling tile wrapping, you can have tiles which size exceed the texture size and will repeat instead of displaying clamped pixels.
	**/
	public var tileWrap(default, set) : Bool;

	/**
		Setting a colorKey color value will discard all pixels that have this exact color in the tile.
	**/
	public var colorKey(default, set) : Null<Int>;

	/**
		Setting a colorMatrix will apply a color transformation. See also `adjustColor`.
	**/
	public var colorMatrix(get, set) : Null<h3d.Matrix>;

	/**
		Setting colorAdd will add the amount of color of each channel R,G,B,A to the object pixels.
	**/
	public var colorAdd(get, set) : Null<h3d.Vector>;

	var shaders : hxsl.ShaderList;

	function new(parent : h2d.Object) {
		super(parent);
		color = new h3d.Vector(1, 1, 1, 1);
	}

	function set_tileWrap(b) {
		return tileWrap = b;
	}

	function get_colorAdd() {
		var s = getShader(h3d.shader.ColorAdd);
		return s == null ? null : s.color;
	}

	function set_colorAdd( c : h3d.Vector ) {
		var s = getShader(h3d.shader.ColorAdd);
		if( s == null ) {
			if( c != null ) {
				s = addShader(new h3d.shader.ColorAdd());
				s.color = c;
			}
		} else {
			if( c == null )
				removeShader(s);
			else
				s.color = c;
		}
		return c;
	}

	override function drawFiltered(ctx:RenderContext, tile:Tile) {
		var old = shaders;
		shaders = null;
		super.drawFiltered(ctx, tile);
		shaders = old;
	}

	function set_colorKey(v:Null<Int>) {
		var s = getShader(h3d.shader.ColorKey);
		if( s == null ) {
			if( v != null )
				s = addShader(new h3d.shader.ColorKey(0xFF000000 | v));
		} else {
			if( v == null )
				removeShader(s);
			else
				s.colorKey.setColor(0xFF000000 | v);
		}
		return colorKey = v;
	}

	/**
		Set the `colorMatrix` value by specifying which effects to apply. Calling adjustColor() reset the colorMatrix to `null`
	**/
	public function adjustColor( ?col : h3d.Matrix.ColorAdjust ) : Void {
		if( col == null )
			colorMatrix = null;
		else {
			var m = colorMatrix;
			if( m == null ) {
				m = new h3d.Matrix();
				colorMatrix = m;
			}
			m.identity();
			m.adjustColor(col);
		}
	}

	function get_colorMatrix() {
		var s = getShader(h3d.shader.ColorMatrix);
		return s == null ? null : s.matrix;
	}

	function set_colorMatrix(m:h3d.Matrix) {
		var s = getShader(h3d.shader.ColorMatrix);
		if( s == null ) {
			if( m != null ) {
				s = addShader(new h3d.shader.ColorMatrix());
				s.matrix = m;
			}
		} else {
			if( m == null )
				removeShader(s);
			else
				s.matrix = m;
		}
		return m;
	}

	/**
		Return the built shader code, can be used for debugging shader assembly
	**/
	public function getDebugShaderCode( toHxsl = true ) {
		var shader = @:privateAccess {
			var ctx = getScene().ctx;
			ctx.manager.compileShaders(new hxsl.ShaderList(ctx.baseShader,shaders));
		}
		if( toHxsl ) {
			var toString = hxsl.Printer.shaderToString.bind(_, true);
			return "// vertex:\n" + toString(shader.vertex.data) + "\n\nfragment:\n" + toString(shader.fragment.data);
		} else {
			return h3d.Engine.getCurrent().driver.getNativeShaderCode(shader);
		}
	}

	/**
		Return the first shader of the given shader class among the object shaders
	**/
	public function getShader< T:hxsl.Shader >( stype : Class<T> ) : T {
		if (shaders != null) for( s in shaders ) {
			var s = hxd.impl.Api.downcast(s, stype);
			if( s != null )
				return s;
		}
		return null;
	}

	/**
		Return all object shaders
	**/
	public inline function getShaders() {
		return shaders.iterator();
	}

	/**
		Add a shader to the object shaders
	**/
	public function addShader<T:hxsl.Shader>( s : T ) : T {
		if( s == null ) throw "Can't add null shader";
		shaders = hxsl.ShaderList.addSort(s, shaders);
		return s;
	}

	/**
		Remove a shader from the object shaders, returns true if found or false if it was not part of our shaders.
	**/
	public function removeShader( s : hxsl.Shader ) {
		var prev = null, cur = shaders;
		while( cur != null ) {
			if( cur.s == s ) {
				if( prev == null )
					shaders = cur.next;
				else
					prev.next = cur.next;
				return true;
			}
			prev = cur;
			cur = cur.next;
		}
		return false;
	}

	override function emitTile( ctx : RenderContext, tile : Tile ) {
		if( tile == null )
			tile = new Tile(null, 0, 0, 5, 5);
		if( !ctx.hasBuffering() ) {
			if( !ctx.drawTile(this, tile) ) return;
			return;
		}
		if( !ctx.beginDrawBatch(this, tile.getTexture()) ) return;

		var alpha = color.a * ctx.globalAlpha;
		var ax = absX + tile.dx * matA + tile.dy * matC;
		var ay = absY + tile.dx * matB + tile.dy * matD;
		var buf = ctx.buffer;
		var pos = ctx.bufPos;
		buf.grow(pos + 4 * 8);

		inline function emit(v:Float) buf[pos++] = v;

		emit(ax);
		emit(ay);
		emit(tile.u);
		emit(tile.v);
		emit(color.r);
		emit(color.g);
		emit(color.b);
		emit(alpha);


		var tw = tile.width;
		var th = tile.height;
		var dx1 = tw * matA;
		var dy1 = tw * matB;
		var dx2 = th * matC;
		var dy2 = th * matD;

		emit(ax + dx1);
		emit(ay + dy1);
		emit(tile.u2);
		emit(tile.v);
		emit(color.r);
		emit(color.g);
		emit(color.b);
		emit(alpha);

		emit(ax + dx2);
		emit(ay + dy2);
		emit(tile.u);
		emit(tile.v2);
		emit(color.r);
		emit(color.g);
		emit(color.b);
		emit(alpha);

		emit(ax + dx1 + dx2);
		emit(ay + dy1 + dy2);
		emit(tile.u2);
		emit(tile.v2);
		emit(color.r);
		emit(color.g);
		emit(color.b);
		emit(alpha);

		ctx.bufPos = pos;
	}

}
